/*
 * ir.c
 *
 * Implements mapping of a Pascal program to an intermediate representation
 *
 * Edited by: Jeremy Hansen, RANDY Compton
 */

#include "ir_map_pascal.h"
#include "semantic.h"

// Prototypes
struct ir_t *map_sequence(struct ir_t *parent, struct statement_sequence_t *ss);
struct ir_t *map_statement(struct ir_t *parent, struct statement_t *s);
char *map_expression(struct ir_t *block, struct expression_t *e);
char *map_simple_expression(struct ir_t *block, struct simple_expression_t *e);
char *map_term(struct ir_t *block, struct term_t *e);
char *map_factor(struct ir_t *block, struct factor_t *e);
char *map_primary(struct ir_t *block, struct primary_t *e, int from_assignment);
char *lowerize(char *s);

struct ir_t *curBlock; // Current block

/* ----------------------------------------------------------------------- 
 * Map a pascal program to the intermediate representation
 * ----------------------------------------------------------------------- 
 */
struct ir_list_t *ir_map_pascal_create(struct program_t *p)
{
	global_ir_list = malloc(sizeof(struct ir_list_t));
	struct ir_list_t *ret = malloc(sizeof(struct ir_list_t));
	struct ir_list_t *list = ret;
	struct class_list_t *cl = p->cl;
	while (cl != NULL) {
		struct func_declaration_list_t *fdl = cl->cb->fdl;
		while (fdl != NULL) {
			struct ir_t *root = bbl_new(NULL); // Create the root
			root->className = cl->ci->id;
			curBlock = root; // Make it current
			char *name = tolower(fdl->fd->fh->id);
			statement_new(&curBlock->statements, TAC_ENTER, "", name, "");
			map_sequence(curBlock, fdl->fd->fb->ss); // Map the code
			statement_new(&curBlock->statements, TAC_RETURN, "", name, "");
			if (list->ir == NULL) {
				list->ir = root;
			} else {
				struct ir_list_t *new = malloc(sizeof(struct ir_list_t));
				list->next = new;
				list = new;
				list->ir = root;
			}
			fdl = fdl->next;
		}
		cl = cl->next;
	}
	return ret;
}

void add_to_global_ir_list(struct ir_t *block) {
	struct ir_list_t *temp = global_ir_list;
	if (temp->ir == NULL) {
		global_ir_list->ir = block;
	} else {
		while (temp->next != NULL) {
			temp = temp->next;
		}
		struct ir_list_t *newIR = malloc(sizeof(struct ir_list_t));
		newIR->ir = block;
		temp->next = newIR;
	}
}

/* ----------------------------------------------------------------------- 
 * Map a sequence of statements into basic block(s)
 * ----------------------------------------------------------------------- 
 */
struct ir_t *map_sequence(struct ir_t *block, struct statement_sequence_t *ss) {
	while(ss != NULL) {
		block = map_statement(block, ss->s);
		ss = ss->next;
	}
	return block;
}

char *get_va_class_name(struct variable_access_t *va) {
	char *v1;
	switch(va->type) {
		case VARIABLE_ACCESS_T_IDENTIFIER:{
			// Check to see if this is a function call
			struct function_designator_t *newFD = malloc(sizeof(struct function_designator_t));
			newFD->id = va->data.id;
			struct symbol_t *f = symtab_function_desig_lookup(newFD, curBlock->className);
			if (f != NULL) {
				// It's a function! Return the return type of it
				return f->data.fd->fd->fh->res;		
			}
			if (strcasecmp(va->data.id,"this") == 0) return curBlock->className;
			struct symbol_t *s = symtab_lookup(tolower(va->data.id), curBlock->className, NULL, SYMTAB_VARIABLE);
			return s->data.type_name;}
		case VARIABLE_ACCESS_T_INDEXED_VARIABLE:
			// Get the type of array this is from the symbol table
			// TODO: IMPLEMENT THIS!
		case VARIABLE_ACCESS_T_ATTRIBUTE_DESIGNATOR: {
			// This designates some sort of attribute
			// First, see if the id is a function
			struct function_designator_t *newFD = malloc(sizeof(struct function_designator_t));
			newFD->id = va->data.ad->id;
			struct symbol_t *f = NULL;
			char *functionClassName = get_va_class_name(va->data.ad->va);
			f = symtab_function_desig_lookup(newFD, functionClassName);
			if (f != NULL) {
				// It's a function! Return the return type of it
				return f->data.fd->fd->fh->res;		
			} else {
				// Not a function, just a lame old variable
				struct symbol_t *s = symtab_lookup(tolower(va->data.ad->id), functionClassName, NULL, SYMTAB_VARIABLE);
			        return s->data.type_name;	
			}
							     }
		case VARIABLE_ACCESS_T_METHOD_DESIGNATOR:	
			// TODO: Implement functions!!!!
			break;		
	}	

}

/* ----------------------------------------------------------------------- 
 * Map the variable access to return the correct character
 * ----------------------------------------------------------------------- 
 */
char *map_variable_access(struct variable_access_t *va, int from_rhs) {
	char *v1;
	switch(va->type) {
		case VARIABLE_ACCESS_T_IDENTIFIER: {
			// First, see if the id is a function
			struct function_designator_t *newFD = malloc(sizeof(struct function_designator_t));
			newFD->id = va->data.id;
			struct symbol_t *f = symtab_function_desig_lookup(newFD, curBlock->className);
			if (f != NULL && from_rhs == 1) {
				// It's a function! Let's add this to the three address code!!
				statement_new(&curBlock->statements, TAC_FUNC, "", va->data.id, curBlock->className);	
				v1 = temp_new();
				statement_new(&curBlock->statements, TAC_END_FUNC, v1, va->data.id, curBlock->className);
				return v1;
			}
			// This is just an id, so return it	
			return va->data.id;}
		case VARIABLE_ACCESS_T_INDEXED_VARIABLE:
			// This is an array of some sort
			v1 = temp_new();
			statement_new(&curBlock->statements, TAC_AT, v1, map_variable_access(va->data.iv->va, 0), map_expression(curBlock, va->data.iv->iel->e));
			return v1;	
		case VARIABLE_ACCESS_T_ATTRIBUTE_DESIGNATOR: {
			// This designates some sort of attribute
			// First, see if the id is a function
			struct function_designator_t *newFD = malloc(sizeof(struct function_designator_t));
			newFD->id = va->data.ad->id;
			struct symbol_t *f = NULL;
			char *functionClassName = get_va_class_name(va->data.ad->va);
			f = symtab_function_desig_lookup(newFD, functionClassName);
			if (f != NULL) {
				// It's a function! Let's add this to the three address code!!
				v1 = temp_new();
				char *caller = map_variable_access(va->data.md->va, 0);
				statement_new(&curBlock->statements, TAC_FUNC, "", va->data.ad->id, caller);	
				statement_new(&curBlock->statements, TAC_END_FUNC, v1, va->data.ad->id, caller);
			} else if (va->data.ad->va->data.id != NULL && strcmp(va->data.ad->va->data.id,"this") == 0) {
				return va->data.ad->id;
			} else {
				// Not a function, just a lame old variable
				v1 = temp_new();
				statement_new(&curBlock->statements, TAC_AT, v1, map_variable_access(va->data.ad->va, 0), va->data.ad->id);
			}
			return v1;}
		case VARIABLE_ACCESS_T_METHOD_DESIGNATOR: {
			// This is a function with parameters
			struct symbol_t *f = NULL;
			char *functionClassName = get_va_class_name(va->data.md->va);
			char *caller = map_variable_access(va->data.md->va, 0);
			f = symtab_function_desig_lookup(va->data.md->fd, functionClassName);
			if (f != NULL) {
				// It's a function! Let's add this to the three address code!!
				statement_new(&curBlock->statements, TAC_FUNC, "", va->data.md->fd->id, caller);
				map_param_list(va->data.md->fd->apl, f->data.fd->fd->fh->fpsl, f->scope, functionClassName);
				v1 = temp_new();
				statement_new(&curBlock->statements, TAC_END_FUNC, v1, va->data.md->fd->id, caller);	
			} else {
				// Dude, we got some errors in here! O.O
				v1 = temp_new();
			}
			return v1;}
	}	
}

char *map_function_designator(struct function_designator_t *fd) {
	struct symbol_t *f = symtab_function_desig_lookup(fd, curBlock->className);
	char *v1;
	if (f != NULL) {
		statement_new(&curBlock->statements, TAC_FUNC, "", fd->id, curBlock->className);
		map_param_list(fd->apl, f->data.fd->fd->fh->fpsl, f->scope, curBlock->className);
		v1 = temp_new();
		statement_new(&curBlock->statements, TAC_END_FUNC, v1, fd->id, curBlock->className);	
	} else {
		// Missing a function, we are! :-(
		v1 = temp_new();
	}
	return v1;
}

void map_param_list(struct actual_parameter_list_t *apl, struct formal_parameter_section_list_t *fpl, int scope, char *funcClassName) {
	// Actual parameter list used to set the values
	// Formal parameter list used to designate where to set the actual parameter values
	struct actual_parameter_list_t *tempApl = apl;
	struct formal_parameter_section_list_t *tempFpl = fpl;
	while (tempApl != NULL && tempFpl != NULL) {
		// Cycle through the formal parameter list and the actual parameter list
		int aplAdvanced = 0;
		struct identifier_list_t *il = tempFpl->fps->il;
		while (il != NULL && tempApl != NULL) {
			// Go through the identifier list in the formal parameter list
			char *fID = il->id;
			char *v1 = temp_new();
			// Get the variable from the symbol table in order to get its offset
			struct symbol_t *s = symtab_variable_lookup(fID, funcClassName, scope);
			// Set a temporary variable to point to the function's parameter offset
			const_statement_new(&curBlock->statements, TAC_PARAM, v1, (int)s->offset);
			// Set the value of the parameter
			statement_new(&curBlock->statements, TAC_SET, v1, map_expression(curBlock, tempApl->ap->e1), "");
			il = il->next;
			tempApl = tempApl->next;
			aplAdvanced = 1;
		}	
		if (aplAdvanced == 0 && tempApl != NULL) tempApl = tempApl->next;
		tempFpl = tempFpl->next;
	}
}	

/* ----------------------------------------------------------------------- 
 * Map a statement into a statement &/or basic block(s)
 * ----------------------------------------------------------------------- 
 */
struct ir_t *map_statement(struct ir_t *parent, struct statement_t *s) {
	switch(s->type) {
		case STATEMENT_T_ASSIGNMENT:
			if (s->data.as->e != NULL) {
				// Assign it
				statement_new(&curBlock->statements, TAC_SET, map_variable_access(s->data.as->va, 0),
						// Map the RHS
						map_expression(curBlock, s->data.as->e), "");
			} else if(s->data.as->oe != NULL) {
				// We are making a new object! How exciting!
				char *caller = map_variable_access(s->data.as->va, 0);
				statement_new(&curBlock->statements, TAC_NEW, caller, s->data.as->oe->id, "");
				if (s->data.as->oe->apl != NULL) {
					// This constructor has parameters - deal with it! First get the function symbol to get the fpl
					struct function_designator_t *newFD = malloc(sizeof(struct function_designator_t));
					newFD->id = s->data.as->oe->id;
					newFD->apl = s->data.as->oe->apl;
					struct symbol_t *f = symtab_function_desig_lookup(newFD, s->data.as->oe->id);
					statement_new(&curBlock->statements, TAC_FUNC, "", s->data.as->oe->id, caller);	
					map_param_list(s->data.as->oe->apl, f->data.fd->fd->fh->fpsl, f->scope, s->data.as->oe->id);
					statement_new(&curBlock->statements, TAC_END_FUNC, caller, s->data.as->oe->id, caller);
				}
			}
			temp_new(); // Throw away a variable name to match test cases (?!)
			return curBlock;
		case STATEMENT_T_SEQUENCE: return map_sequence(parent, s->data.ss);
		case STATEMENT_T_IF: {
					     char * tempClName;
					     tempClName = curBlock->className;
					     struct ir_t *iBlock, *tEnd, *eStart;
					     curBlock = curBlock->next = bbl_new(parent); // Create expression block
					     curBlock->className = tempClName;
					     iBlock = curBlock; // Save it
					     iBlock->cond = map_expression(curBlock, s->data.is->e); // Map expression
					     curBlock = curBlock->next = bbl_new(iBlock); // Create THEN block
					     curBlock->className = tempClName;
					     iBlock->otherChild = curBlock; // Make it the YES branch
					     parent = map_statement(iBlock, s->data.is->s1); // Map the THEN branch
					     tEnd = curBlock; // Save the end of it
					     curBlock = curBlock->next = bbl_new(iBlock); // Create ELSE block
					     curBlock->className = tempClName;
					     eStart = map_statement(iBlock, s->data.is->s2); // Map the ELSE branch
					     tEnd->nextParent = eStart; // Link the branches as sibling parents
					     curBlock = curBlock->next = bbl_new(parent); // Create following block
					     curBlock->className = tempClName;
					     return curBlock;
				     }
		case STATEMENT_T_WHILE: {
						char * tempCl;
						tempCl = curBlock->className;
						curBlock = curBlock->next = bbl_new(NULL); // Create expression block
						curBlock->className = tempCl;
						struct ir_t *cBlock = curBlock; // Save it for later
						cBlock->cond = map_expression(curBlock, s->data.ws->e); // Map expression
						curBlock = curBlock->next = bbl_new(parent); // Create loop body block
						curBlock->className = tempCl;
						struct ir_t *temp = map_statement(parent, s->data.ws->s); // Map loop body
						curBlock->nextParent = parent; // Link with the loop entry points
						// Set all parents' children
						for(; temp != NULL; temp = temp->nextParent) temp->child = cBlock;
						curBlock = curBlock->next = bbl_new(cBlock); // Create following block
						curBlock->className = tempCl;
						return curBlock;
					}
		case STATEMENT_T_PRIMARY: {
						  map_primary(curBlock, s->data.pr, 1);
						  return curBlock;
					  }
		case STATEMENT_T_PRINT:
					  statement_new(&curBlock->statements, TAC_PRINT, "", map_variable_access(s->data.ps->va, 0), "");
					  return curBlock;
	}
}

/* ----------------------------------------------------------------------- 
 * Map an expression into three-address code statements (returns the name
 * of the temporary variable used to hold the result)
 * ----------------------------------------------------------------------- 
 */
char *map_expression(struct ir_t *block, struct expression_t *e) {
	char *se1 = map_simple_expression(block, e->se1);
	if(e->se2 != NULL) { // If it has an operator, do the other operand

		char *se2 = map_simple_expression(block, e->se2);
		char *result = temp_new();
		statement_new(&block->statements, e->relop == EQUAL ? TAC_EQUAL :
				e->relop == NOTEQUAL ? TAC_NOTEQ : e->relop == LT ? TAC_LT : 
				e->relop == GT ? TAC_GT : e->relop == LE ? TAC_LEQ : TAC_GEQ, result, se1,
				se2);
		return result;
	} else return se1; // Otherwise just the simple expression
}

/* ----------------------------------------------------------------------- 
 * Map a simple expression into three-address code statements
 * ----------------------------------------------------------------------- 
 */
char *map_simple_expression(struct ir_t *block, struct simple_expression_t *e) {
	char *t1 = map_term(block, e->t);
	while(e->next != NULL) {
		int addop = e->addop; // Save the operator
		e = e->next;
		char *t2 = map_term(block, e->t);
		char *result = temp_new();
		statement_new(&block->statements, addop == PLUS ? TAC_PLUS :
				addop == MINUS ? TAC_MINUS : TAC_OR, result, t1, t2);
		t1 = result;
	}
	return t1;
}

/* ----------------------------------------------------------------------- 
 * Map a term into three-address code statements
 * ----------------------------------------------------------------------- 
 */
char *map_term(struct ir_t *block, struct term_t *e) {
	char *f1 = map_factor(block, e->f);
	while(e->next != NULL) {
		printf("multi \n");
		int mulop = e->mulop; // Save the operator
		e = e->next;
		char *f2 = map_factor(block, e->f);
		char *result = temp_new();
		statement_new(&block->statements, mulop == STAR ? TAC_TIMES :
				mulop == SLASH ? TAC_DIV : mulop == MOD ? TAC_MOD : TAC_AND, result, f1,
				f2);
		f1 = result;
	}
	return f1;
}

/* ----------------------------------------------------------------------- 
 * Map a factor into three-address code statements
 * ----------------------------------------------------------------------- 
 */
char *map_factor(struct ir_t *block, struct factor_t *e) {
	if(e->type == FACTOR_T_SIGNFACTOR) {
		char *f = map_factor(block, e->data.f.next);
		if(*e->data.f.sign == MINUS) { // Negate if it was a minus sign
			char *result = temp_new();
			statement_new(&block->statements, TAC_NEGATE, result, f, "");
			return result;
		} else return f;
	} else {
		return map_primary(block, e->data.p, 0);
	}
}

/* ----------------------------------------------------------------------- 
 * Map a primary into three-address code statements
 * ----------------------------------------------------------------------- 
 */
char *map_primary(struct ir_t *block, struct primary_t *e, int from_assignment) {
	switch(e->type) {
		case PRIMARY_T_VARIABLE_ACCESS: { // Retrieve the variable
							if (from_assignment == 0){
								char *result = temp_new();
								statement_new(&block->statements, TAC_SET, result, map_variable_access(e->data.va, 1),
									"");
								return result;
							}
							else return map_variable_access(e->data.va, 1);
						}
		case PRIMARY_T_UNSIGNED_CONSTANT: { // Store the constant
							  char *result = temp_new();
							  const_statement_new(&block->statements, TAC_SET, result, e->data.un->ui);
							  return result;
						  }
		case PRIMARY_T_EXPRESSION: return map_expression(block, e->data.e);
		case PRIMARY_T_PRIMARY: { // NOT the primary
						char *f = map_primary(block, e->data.p.next, 0);
						char *result = temp_new();
						statement_new(&block->statements, TAC_NOT, result, f, "");
						return result;
					}
		case PRIMARY_T_FUNCTION_DESIGNATOR :
					   return map_function_designator(e->data.fd);
		default: return "C is being annoying again.";
	}
}
